﻿using System;
using System.Collections.Generic;
using System.Reflection;
using AppWorks.Config;
using System.Diagnostics.Contracts;
using AppWorks.Exceptions;

namespace AppWorks
{
    public class Environment
    {
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        private static readonly Dictionary<Type, Object> _defaults = new Dictionary<Type, object>(0);

        static Environment()
        {
            InitDefaults();
        }

        private static void InitDefaults()
        {
            SetDefault<IClock>(new DateTimeBasedClock());
            SetDefault<IUniqueIdentifierGenerator>(new BasicGuidGenerator());
        }

        private static IEnvironmentConfiguration _instance;

        public static T Get<T>() where T : class
        {
            Contract.Ensures(Contract.Result<T>() != null, "The result cannot be null.");

            Log.DebugFormat("Requesting instance {0} from the environment.", typeof(T).FullName);

            T result = null;

            if (_instance == null || !_instance.TryGet(out result))
            {
                object defaultResult;

                if (_defaults.TryGetValue(typeof(T), out defaultResult))
                {
                    result = (T)defaultResult;

                }
            }

            if (result == null)
                throw new InstanceNotFoundInEnvironmentConfigurationException(typeof(T));

            return result;
        }

        public static void SetDefault<T>(T instance) where T : class
        {
            Contract.Requires<ArgumentNullException>(instance != null, "The instance cannot be null.");

            _defaults[typeof(T)] = instance;
        }

        public static void RemoveDefault<T>() where T : class
        {
            _defaults.Remove(typeof(T));
        }

        public static void Configure(IEnvironmentConfiguration source)
        {
            Contract.Requires<ArgumentNullException>(source != null, "The source cannot be null.");
            Contract.Requires<InvalidOperationException>(!IsConfigured, "Cannot configure the environment when it is already configured.");
            Contract.Ensures(_instance == source, "The given source should initialize the _instance member.");
            Contract.Ensures(IsConfigured, "The given source should configure this environment.");

            _instance = source;

            Log.InfoFormat("Ncqrs environment configured with {0} configuration source.", source.GetType().FullName);
        }

        public static void Deconfigure()
        {
            _instance = null;
            _defaults.Clear();

            InitDefaults();
        }

        public static Boolean IsConfigured
        {
            get
            {
                return _instance != null;
            }
        }

        public static IEnvironmentConfiguration CurrentConfiguration { get { return _instance; } }
    }
}
